/** for BasicStroke */
import java.awt.BasicStroke;

/** for Color */
import java.awt.Color;

/** for Dimension */
import java.awt.Dimension;

/** for Font */
import java.awt.Font;

/** for Graphics */
import java.awt.Graphics;

/** for Graphics2D */
import java.awt.Graphics2D;

/** for ActionEvent */
import java.awt.event.ActionEvent;

/** for ActionListener */
import java.awt.event.ActionListener;

/** for KeyEvent */
import java.awt.event.KeyEvent;

/** for KeyListener */
import java.awt.event.KeyListener;

/** for ArrayList */
import java.util.ArrayList;

/** for List */
import java.util.List;

/** for Random */
import java.util.Random;

/** for BorderFactory */
import javax.swing.BorderFactory;

/** for JOptionPane */
import javax.swing.JOptionPane;

/** for JPanel */
import javax.swing.JPanel;

/** for Timer */
import javax.swing.Timer;

/**
 * HW 12b & 13b, "PlayingPanel"<br><br>
 * 
 * This class represents the main board panel where the tetris game is drawn on. 
 * It contains the timer to update the information,
 * and other methods to check and update other status as well. 
 * 
 * @author Hyun Seo Chung
 * @version 1.0 04/21/12
 * @see JPanel
 */
public class PlayingPanel extends JPanel {
	
	/**
	 * serialVersionUID for PlayingPanel class
	 */
	private static final long serialVersionUID = 1L;
	
	/**
	 * Dimension for the tiles
	 */
	private Dimension tileDimension;
	
	/**
	 * Main timer
	 */
	private final Timer timer = 
			new Timer(Numbers.THOUSAND.getNumber(), new TimerListener());
	
	/**
	 * Random generator
	 */
	private final Random rand = new Random();
	
	/**
	 * Current piece and the next piece
	 */
	private Piece piece, nextPiece;
	
	/**
	 * Current PlayingField
	 */
	private static final PlayingField FIELD = new PlayingField();
	
	/**
	 * Current ArrayList for the pieces
	 */
	private List<Piece> pieceList = new ArrayList<Piece>();
	
	/**
	 * Current ControlPanel
	 */
	private ControlPanel c_panel;
	
	/**
	 * Current value of paused
	 */
	private boolean paused;
	
	/** for TWO **/
	private static final int TWO = 2;
	
	/** for THREE **/
	private static final int THREE = 3;
	
	/** for FOUR **/
	private static final int FOUR = 4;
	
	/** for FIVE **/
	private static final int FIVE = 5;

	/** for SIX **/
	private static final int SIX = 6;
	
	/**
	 * This is a constructor for the PlayingPanel, 
	 * where it sets up the panel's layout, preferredSize, and the initial tileDimension.
	 * It declares and initiate the timer, KeyListener, 
	 * and the current piece and the next piece as well.  
	 */
	public PlayingPanel() {
		super.setLayout(null);
		super.setPreferredSize(
				new Dimension(Numbers.TWO_HUNDRED_FIFTY.getNumber() + 1, 
						Numbers.SEVEN_HUNDRED.getNumber() 
						+ Numbers.TWENTY_SEVEN.getNumber()));
		super.setBorder(BorderFactory.createEmptyBorder(0, 
				Numbers.TWENTY.getNumber(), 0, 0));
		tileDimension = 
				new Dimension(super.getWidth() / Numbers.TEN.getNumber(), 
						super.getHeight() / Numbers.TWENTY_TWO.getNumber());
		
		// sets up the timer for every second
//		timer = new Timer(1000, new TimerListener());
		timer.start();
		
		// sets up the key listener
		super.setFocusable(true);
		super.addKeyListener(new KeyboardAction());
		
		// adds the initial random piece
		piece = newPiece();
		pieceList.add(piece);
		nextPiece = newPiece();
	}
	
	/**
	 * This method creates a new piece randomly between any of the seven possible shapes. 
	 * 
	 * @return The new random piece created
	 */
	private Piece newPiece() {
		
		Piece p_temp = null;
		
		switch (rand.nextInt(Numbers.SEVEN.getNumber())) {
		case 0:
			p_temp = new LinePiece(Numbers.THREE.getNumber(), -1);
			break;
		case 1:
			p_temp = new SquarePiece(Numbers.THREE.getNumber(), -1);
			break;
		case TWO:
			p_temp = new J_Piece(Numbers.THREE.getNumber(), -1);
			break;
		case THREE:
			p_temp = new L_Piece(Numbers.THREE.getNumber(), -1);
			break;
		case FOUR:
			p_temp = new S_Piece(Numbers.THREE.getNumber(), -1);
			break;
		case FIVE:
			p_temp = new Z_Piece(Numbers.THREE.getNumber(), -1);
			break;
		case SIX:
			p_temp = new T_Piece(Numbers.THREE.getNumber(), -1);
			break;
		default:
			break;
	}
		
		return p_temp;
	}
	
	/**
	 * This is a method that paints the graphics on the panel
	 * 
	 * @param g The Graphics object to be drawn on
	 */
	public void paintComponent(Graphics g) {
		
		super.paintComponent(g);
		
		final Graphics2D g2 = (Graphics2D) g;
		
		// for the grid
		tileDimension = 
				new Dimension(this.getWidth() / Numbers.TEN.getNumber(), 
						this.getHeight() / Numbers.TWENTY_TWO.getNumber()); 
		for (int i = 0; i < Numbers.TWENTY_TWO.getNumber(); i++) {
			for (int j = 0; j < Numbers.TEN.getNumber(); j++) {
				if ((j % Numbers.TWO.getNumber() == 1 
						&& i % Numbers.TWO.getNumber() == 1) 
						|| (j % Numbers.TWO.getNumber() == 0 
						&& i % Numbers.TWO.getNumber() == 0)) {
					g.setColor(
							Color.getHSBColor(0, 0, 
									Numbers.POINT_ONE_SEVEN_FOUR_THREE.getDecimal()));
				} else {
					g.setColor(
							Color.getHSBColor(0, 0, 
									Numbers.POINT_ONE_THREE_EIGHT_SIX.getDecimal()));
				}
				g.fillRect(j * tileDimension.width, i * tileDimension.height, 
							tileDimension.width, tileDimension.height);
				g2.setColor(Color.getHSBColor(0, 0, Numbers.POINT_ONE.getDecimal()));
				g2.setStroke(new BasicStroke(Numbers.THREE.getNumber()));
				g2.drawRect(j * tileDimension.width, i * tileDimension.height, 
							tileDimension.width, tileDimension.height);
			}
		}
		
		piece.draw(g, tileDimension);
		FIELD.draw(g, tileDimension);
		
		paintFinalGrid(g, g2);
	}
	
	/**
	 * to paint final grid
	 * @param g
	 * @param g2
	 */
	private void paintFinalGrid(Graphics g, Graphics2D g2) {
		// for the grid
		g2.setColor(Color.getHSBColor(0, 0, Numbers.POINT_ONE.getDecimal()));
		for (int i = 0; i < Numbers.TWENTY_TWO.getNumber(); i++) {
			for (int j = 0; j < Numbers.TEN.getNumber(); j++) {
				g.drawRect(j * tileDimension.width, i * tileDimension.height, 
						   tileDimension.width, tileDimension.height);
			}
		}
		
		if (paused) {
			for (int i = 0; i < Numbers.TWENTY_TWO.getNumber(); i++) {
				for (int j = 0; j < Numbers.TEN.getNumber(); j++) {
					g.setColor(
							new Color(0, 0, 0, 
									Numbers.HUNDRED_TWENTY_EIGHT.getNumber()));
					g.fillRect(j * tileDimension.width, i * tileDimension.height, 
								tileDimension.width, tileDimension.height);
				}
			}
			g.setColor(Color.white);
			g.setFont(new Font("OCR A Std", Font.BOLD, Numbers.EIGHTY.getNumber()));
			g.drawString("PAUSED", 
					Numbers.FORTY.getNumber(), 
					Numbers.THREE_HUNDRED_FIFTY.getNumber());
		}
	}
	
	/**
	 * This class is a TimerListener that implements the ActionListener, 
	 * for the main timer in the game. 
	 * When it is called, it attempts to move the piece down by one block, 
	 * and settles it if it was not able to move anymore. 
	 * It then also checks for any possible clear(s), 
	 * and changes the level according to the current score. 
	 * It also checks for the loss condition. 
	 * 
	 * @author Hyun Seo Chung
	 * @version 1.0 04/21/12
	 */
	private class TimerListener implements ActionListener {
		
		/**
		 * this is action performed
		 * @param e
		 */
		@Override
		public void actionPerformed(ActionEvent e) {
			
			if (!piece.canMove(0, 1)) {
				piece.settle();
				piece = newPiece();
				pieceList.add(piece);
				piece = nextPiece;
				nextPiece = newPiece();
			}
			FIELD.checkForClears();
			FIELD.changeLevel();
			if (FIELD.getLevel() <= Numbers.ELEVEN.getNumber()) {
				timer.setDelay(Numbers.THOUSAND.getNumber() 
						+ Numbers.HUNDRED.getNumber() 
						- FIELD.getLevel() * Numbers.HUNDRED.getNumber());
			} else {
				timer.setDelay(0);
			}
			if (FIELD.isLossConditionMet()) {
				final int ans = JOptionPane.showConfirmDialog(
						null, "You Lost!\nWould you like to play again?", "Play Again?", 
						JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
				if ( ans == JOptionPane.YES_OPTION ) {
					// restart the game
					pieceList = new ArrayList<Piece>();
//					field = new PlayingField();
					FIELD.resetField();
					piece = newPiece();
					pieceList.add(piece);
				} else {
					// terminate the application
					System.exit(0);
				}
			}
			c_panel.repaint();
		}
		
		/** 
		 * This is a tostring method.
		 * @return tostring.
		 */
		@Override
		public String toString() {
			return super.toString();
		}
	}
	
	/**
	 * This class is a KeyListener that listens for any key press(es) and performs 
	 * a method correspondingly if the correct key is pressed. 
	 * 
	 * @author Hyun Seo Chung
	 * @version 1.0 04/21/12
	 */
	private class KeyboardAction implements KeyListener {
		
		/**
		 * This method is executed when the key is pressed, 
		 * and determines which method to perform. 
		 * 
		 * @param e The KeyEvent object containing the information about the key pressed 
		 */
		@Override
		public void keyPressed (KeyEvent e) {
			final int keyCode = e.getKeyCode();
			boolean ul = false;
			if (!paused) {
				switch (keyCode) {
					case KeyEvent.VK_SPACE:
						while (piece.canMove(0, 1)) {
							System.out.print("");
						}
						break;
					case KeyEvent.VK_UP:
						ul = piece.canRotate();
						break;
					case KeyEvent.VK_LEFT:
						ul = piece.canMove(-1, 0);
						break;
					case KeyEvent.VK_RIGHT:
						ul = piece.canMove(1, 0);
						break;
					case KeyEvent.VK_DOWN:
						ul = piece.canMove(0, 1);
						break;
					case KeyEvent.VK_ESCAPE:
						pauseGame();
						break;
					default: 
						break;
				}
			} else if (keyCode == KeyEvent.VK_ESCAPE) {
				resume();
			}
			System.out.print(ul);
			c_panel.repaint();
		}
		
		/**
		 * This is to handle key released
		 * @param e
		 */
		@Override
		public void keyReleased (KeyEvent e) {
			System.out.print("");
		}
		
		/**
		 * This is to handle key typed
		 * @param e
		 */
		@Override
		public void keyTyped (KeyEvent e) {
			System.out.print("");
		}
		
		/** 
		 * This is a tostring method.
		 * @return tostring.
		 */
		@Override
		public String toString() {
			return super.toString();
		}
	}
	
	/**
	 * This method pauses the current game, 
	 * setting the boolean paused value to true, 
	 * stops the timer, and repaints the panel.  
	 */
	public void pauseGame() {
		paused = true;
		timer.stop();
		repaint();
	}
	
	/**
	 * This method resumes the current game, 
	 * setting the boolean paused value to false, 
	 * starts the timer, and repaints the panel. 
	 */
	public void resume() {
		paused = false;
		timer.start();
		repaint();
	}
	
	/**
	 * This is a getter that returns the reference 
	 * of the current PlayingField being used. 
	 * 
	 * @return The reference of the current PlayingField being used
	 */
	public static PlayingField getField() {
		return FIELD;
	}

	/**
	 * This is a getter that returns the reference of the current next piece created. 
	 * 
	 * @return The reference of the current next piece created
	 */
	public Piece getNextPiece() {
		return nextPiece;
	}
	
	/**
	 * This is a getter for the current tile dimension being used. 
	 * 
	 * @return The reference of the current tile dimension value
	 */
	public Dimension getTileDimension() {
		return tileDimension;
	}
	
	/**
	 * This is a setter to set up the reference to the current control panel being used. 
	 * 
	 * @param p The reference to the current ControlPanel being used
	 */
	public void setControlPanel(ControlPanel p) {
		c_panel = p;
	}
	
}
